package com.gitee.wsl.mathematics.function.scale.discrete.banded

import com.gitee.wsl.mathematics.function.scale.DiscreteDomain
import com.gitee.wsl.mathematics.function.scale.Scale
import com.gitee.wsl.mathematics.function.scale.StrictlyContinuous
import com.gitee.wsl.mathematics.function.scale.StrictlyContinuousRange
import com.gitee.wsl.mathematics.function.scale.Tickable
import com.gitee.wsl.mathematics.function.scale.discrete.IndexableDomain
import com.gitee.wsl.mathematics.function.scale.intervalOf
import com.gitee.wsl.mathematics.interpolator.Percent
import com.gitee.wsl.mathematics.interpolator.PercentOrRatio
import com.gitee.wsl.mathematics.interpolator.pct
import kotlin.math.floor
import kotlin.math.max

 abstract class BandedScale<D>(private val indexableDomain: IndexableDomain<D> = IndexableDomain()) :
    Scale<D, Double>,
    DiscreteDomain<D> by indexableDomain,
     Tickable<D>,
     StrictlyContinuousRange<D, Double> {

    private val unknown = Double.NaN

    protected var _paddingInner: Double = 0.0
    protected var _paddingOuter: Double = 0.0

    abstract var padding: Percent

    override var domain: List<D>
        get() = indexableDomain._domain
        set(value) {
            indexableDomain.domain = value
            rescale()
        }

    override var range: StrictlyContinuous<Double> = intervalOf(0.0, 1.0)
        get() = field
        set(value) {
            field = value
            rescale()
        }

     var round: Boolean = false
        set(value) {
            field = value
            rescale()
        }

    /**
     * Sets the band alignment to the specified value which must be in the range [0%, 100%].
     * [align] specifies how the outer padding in the scale’s range is distributed.
     * The default align = 50% centers the bands within the range, with equal outer padding on both sides.
     */
     var align: PercentOrRatio = 50.pct
        set(value) {
            field = value.coerceToDefault()
            rescale()
        }

     var step: Double = 1.0
        private set

     var bandwidth: Double = 1.0
        private set

    private var ordinalRange: MutableList<Double> = mutableListOf()

    override operator fun invoke(domainValue: D): Double {
        val i: Int = indexableDomain.index[domainValue] ?: return unknown
        return if (ordinalRange.isEmpty()) unknown else ordinalRange[i]
    }

    override fun ticks(count: Int): List<D> = domain

    protected fun rescale() {
        val n = indexableDomain._domain.size
        val reverse = range.end < range.start
        var start = if (reverse) range.end else range.start
        val stop = if (reverse) range.start else range.end
        step = (stop - start) / max(1.0, n - _paddingInner + _paddingOuter * 2)
        if (round)
            step = floor(step)

        start += (stop - start - step * (n - _paddingInner)) * align.value
        bandwidth = step * (1 - _paddingInner)
        if (round) {
            start = kotlin.math.round(start)
            bandwidth = kotlin.math.round(bandwidth)
        }

        val values: Array<Double> = Array(n, { start + step * it })
        if (reverse) values.reverse()
        ordinalRange.clear()
        ordinalRange.addAll(values)
    }
}

